package cn.sc.summer.calculate.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.sc.summer.calculate.math.MathUtil;
import cn.sc.summer.calculate.score.ServerScore;
import cn.sc.summer.calculate.service.ScoreService;
import cn.sc.summer.constant.redis.RedisConstant;
import cn.sc.summer.constant.util.AssertUtil;
import cn.sc.summer.redis.po.ServerInfo;
import cn.sc.summer.redis.po.ServiceInstance;
import cn.sc.summer.redis.util.NacosInstancesService;
import cn.sc.summer.redis.util.RedisHelper;
import cn.sc.summer.system.po.SystemInfoDTO;
import com.alibaba.cloud.nacos.NacosDiscoveryProperties;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

/**
 * 类名：分数计算接口实现
 *
 * @author a-xin
 * @date 2024/1/29 10:26
 */
@Slf4j
@Service
public class ScoreServiceImpl implements ScoreService {

    @Resource
    private ServerScore serverScore;

    @Resource
    private NacosDiscoveryProperties nacosDiscoveryProperties;

    /**
     * 刷新服务分数
     */
    @Override
    public void refreshServerScore() {
        Set<String> serverSystemInfoKeys = RedisHelper.getKeysByPrefix(RedisConstant.SERVER_SYSTEM_INFO);
        AssertUtil.functionIfPre(serverSystemInfoKeys, CollUtil::isNotEmpty, () -> serverSystemInfoKeys.stream().forEach(serverIp -> {
            //计算的单个服务平台的分值
            List<BigDecimal> serverScores = new ArrayList<>();
            List<Object> serverInfo = RedisHelper.lGetAll(serverIp);
            AtomicReference<String> serverName = new AtomicReference<>("");
            AtomicReference<String> serverAddress = new AtomicReference<>("");
            AtomicReference<String> serverNameSpace = new AtomicReference<>("");
            //获取到单个服务的所有资源
            AssertUtil.functionIfPre(serverInfo, CollUtil::isNotEmpty, () ->
                    serverInfo.stream().map(server -> JSONObject.parseObject(server.toString(), SystemInfoDTO.class))
                            .forEach(systemInfoDTO -> {
                                serverScores.add(serverScore.computeScore(systemInfoDTO));
                                serverName.set(systemInfoDTO.getServerName());
                                serverNameSpace.set(systemInfoDTO.getServerNamespace());
                                serverAddress.set(systemInfoDTO.getServerIp() + ":" + systemInfoDTO.getServerPort());
                            }));
            //将每一次的分数求得标准差
            BigDecimal serverStandardDeviation = MathUtil.standardDeviation(serverScores).setScale(4, RoundingMode.HALF_UP);
            log.info("==> The micro service：{}, nameSpace is : {}, and The standard deviation of the service is ：{}",
                    serverName, serverNameSpace, serverStandardDeviation);
            String zRedisKey = RedisConstant.SERVER_SCORE + serverName.get() + ":" + serverNameSpace.get();
            RedisHelper.zAdd(zRedisKey, serverAddress.get(), serverStandardDeviation.doubleValue());
        }));
    }

    /**
     * 刷新服务实例在nacos的权重
     */
    @Override
    public void refreshNacosWeight() {

        //修改服务所在nacos集群权重
        Set<String> serverScoreKeys = RedisHelper.getKeysByPrefix(RedisConstant.SERVER_SCORE);
        AssertUtil.functionIfPre(serverScoreKeys, CollUtil::isNotEmpty, () -> {

            Map<String, List<ServerNameSpace>> nameSpace = serverScoreKeys.stream().map(serverScoreKey -> {
                ServerNameSpace serverNameSpace = new ServerNameSpace();
                String[] split = serverScoreKey.split(":");
                serverNameSpace.setServerName(split[1]);
                serverNameSpace.setNameSpace(split[2]);
                return serverNameSpace;
            }).collect(Collectors.groupingBy(ServerNameSpace::getNameSpace));

            nameSpace.forEach((key, value) -> value.stream().forEach(serverScoreKey -> {
                String serverName = serverScoreKey.getServerName();
                Set<ZSetOperations.TypedTuple<Object>> typedTuples = RedisHelper.zGetAllDES(RedisConstant.SERVER_SCORE + serverName + ":" + serverScoreKey.getNameSpace());
                AssertUtil.functionIf(CollUtil.isNotEmpty(typedTuples) && typedTuples.size() > 1, () -> {
                    this.nacosDiscoveryProperties.setNamespace(serverScoreKey.getNameSpace());
                    List<Double> score = typedTuples.stream().map(ZSetOperations.TypedTuple::getScore).collect(Collectors.toList());
                    Map<Double, Double> scoreMap = MathUtil.calculateWeights(score);
                    typedTuples.stream().forEach(typedTuple -> {
                        Double weight = scoreMap.getOrDefault(typedTuple.getScore(), 1D);
                        String ip = typedTuple.getValue().toString();
                        List<ServiceInstance> allInstanceInfo = NacosInstancesService.getAllInstanceInfo(serverName, this.nacosDiscoveryProperties);
                        AssertUtil.functionIf(CollUtil.isNotEmpty(allInstanceInfo) && allInstanceInfo.size() > 1, () -> allInstanceInfo.stream().forEach(instance -> AssertUtil.functionIf(ip.equals(instance.getIp() + ":" + instance.getPort()), () -> {
                            log.info("The micro service: {} of {}, Update the nacos weight to {} !", instance.getIp() + ":" + instance.getPort(), serverName, weight);
                            instance.setWeight(weight);
                            NacosInstancesService.updateNacosInstance(instance, this.nacosDiscoveryProperties);
                        })));
                    });
                });
            }));
        });

        List<ServerInfo> allServerInfo = NacosInstancesService.getAllServerInfo(nacosDiscoveryProperties);
        AssertUtil.functionIfPre(allServerInfo, CollUtil::isNotEmpty, () -> allServerInfo.stream().forEach(serverInfo -> {
            String serverName = serverInfo.getName();
            Integer healthyInstanceCount = serverInfo.getHealthyInstanceCount();
            if (healthyInstanceCount == 1) {
                List<ServiceInstance> allInstanceInfo = NacosInstancesService.getAllInstanceInfo(serverName, this.nacosDiscoveryProperties);
                ServiceInstance serviceInstance = allInstanceInfo.get(0);
                serviceInstance.setWeight(1D);
                NacosInstancesService.updateNacosInstance(serviceInstance, this.nacosDiscoveryProperties);
                log.info("The micro service: {} only 1 instance, Update the nacos weight to 1 !", serverName);
            }
        }));

    }

    @Data
    static class ServerNameSpace implements Serializable {
        private static final long serialVersionUID = 1L;

        /**
         * 命名空间
         */
        private String nameSpace;

        /**
         * 服务名称
         */
        private String serverName;
    }

}
